<?php

/**
 * SVN操作基础PHP类
 * 
 * @example
  <code>
  $phpsvn = new phpsvn();
  $phpsvn->setOptions(array(
  'username' => 'yourname',
  'password' => 'yourpassword'
  ));
  $phpsvn->setPath($svnpath, $localpath);
  $r = $phpsvn->checkout();
  if($r === false){
  echo $phpsvn->error();
  }else{
  print_r($r);
  }
  </code>
 * @author anyon<cxphp@qq.com>
 * @date 2014/08/13 04:06 
 */
class phpsvn {

	/**
	 * SVN配置文件
	 * 
	 * @var array
	 */
	private $opts = array(
		'username'	 => '',
		'password'	 => '',
		'config-dir' => '', ///usr/home/finance/.subversion
	);

	/**
	 * 错误日志文件 & 需要有写权限
	 * 
	 * @var string
	 */
	private $errorfile = '/tmp/phpsvn.err';

	/**
	 * 错误信息 如果为""则无错误
	 * @var string
	 */
	private $error = '';

	/**
	 * 返回值
	 * 
	 * @var array
	 */
	private $retValue = array();

	/**
	 * Svn 路径
	 * 
	 * @var string
	 */
	private $svnpath = '';

	/**
	 * 本地存储路径
	 * 
	 * @var string
	 */
	private $targetPath = '.';

	/**
	 * actions tags
	 * 
	 * @var array
	 */
	private $shortTags = array('a', 'u', 'd');

	/**
	 * 配置SVN配项
	 * 
	 * @param type $options
	 */
	public function setOptions($options = array()) {
		$this->opts = array_merge($this->opts, $options);
	}

	/**
	 * 设置SVN PATH 及本地存储路径
	 * 
	 * @param string $svnpath svn path
	 * @param string $targetpath local path
	 * @return void
	 */
	public function setPath($svnpath, $targetpath = '.') {
		$this->svnpath = $svnpath;
		$this->targetPath = realpath($targetpath);
	}

	/**
	 * update from server
	 * 
	 * @return mixed array on success or false on error
	 */
	public function update() {
		return $this->doCmd('up');
	}

	/**
	 * commit file to server
	 * 
	 * @return mixed array on success or false on error
	 */
	public function commit() {
		return $this->doCmd('ci');
	}

	/**
	 * Add file to svn
	 * 
	 * @param string $file filename
	 * @return mixed array on success or false on error
	 */
	public function add($file) {
		return $this->doCmd('add', $file);
	}

	/**
	 * Chectout file from svn to local
	 * 
	 * @return mixed array on success or false on error
	 */
	public function checkout() {
		return $this->doCmd('co', $this->svnpath);
		//Checked out revision 240772
	}

	/**
	 * Execute command for svn
	 * 
	 * support commands:add/checkout(co)/cleanup/commit(ci)/copy(cp)/delete(del,remove,rm)/diff(di)/update (up)
	 * todo commands:export
	 * help (?, h)
	 * import
	 * info
	 * list (ls)
	 * lock
	 * log
	 * merge
	 * mkdir
	 * move (mv, rename, ren)
	 * propdel (pdel, pd)
	 * propedit (pedit, pe)
	 * propget (pget, pg)
	 * proplist (plist, pl)
	 * propset (pset, ps)
	 * resolved
	 * revert
	 * status (stat, st)
	 * switch (sw)
	 * @param string $cmd
	 * @param string $param
	 */
	public function doCmd($cmd, $param = '') {
		chdir($this->targetPath);
		$cmd = "{$cmd} {$param}";
		$result = $this->shell($cmd);
		return $this->result($result);
	}

	/**
	 * Error message last time
	 * 
	 * @return string error message
	 */
	public function error() {
		return $this->error;
	}

	/**
	 * Format the result handle
	 * 
	 * @param string $result result string
	 * @return string
	 */
	private function result($result) {
		if ($result === false) {
			return false;
		}
		foreach (explode("\n", $result) as $line) {
			$line = trim($line);
			$this->retLine($line);
		}
		return $this->retValue;
	}

	private function retLine($line) {
		$line = strtolower($line);
		if (empty($line)) {
			return;
		}
		$retValue = array();
		if (in_array($line[0], $this->shortTags)) {
			$retValue['a'] = $line[0];
			$retValue['v'] = trim(substr($line, 2));
		} else {
			preg_match('/([0-9]+)/', $line, $match);
			$num = intval($match[0]);
			if ($num > 0) {
				$retValue['a'] = 'v';
				$retValue['v'] = $num;
			}
		}
		$this->retValue[] = $retValue;
	}

	/**
	 * Get svn file version from result line
	 * 
	 * @param string $line result line
	 * @return mixed version number or false if on error
	 */
	private function getVersionByLine($line) {
		$line = trim(strtolower($line));
		if (preg_match('/([0-9]+)/', $line, $match)) {
			return $match[0];
		}
		return false;
	}

	/**
	 * Exec shell command
	 * 
	 * @access private
	 * @param string $cmd command to be executed
	 * @return string result string should been displayed on stdout, 
	 * @return return false if on error
	 */
	private function shell($cmd) {
		$opts = '';
		foreach ($this->opts as $key => $item) {
			if (!empty($item)) {
				$opts .= "--{$key} {$item} ";
			}
		}
		$result = shell_exec("svn {$opts}" . $cmd . ' 2> ' . $this->errorfile);
		if ($this->isResultError()) {
			return false;
		}
		return $result;
	}

	/**
	 * Check if on error
	 * 
	 * @param string $result shell result string
	 * @return boolen
	 */
	private function isResultError() {
		$this->error = file_get_contents($this->errorfile);
		if (empty($this->error)) {
			return false;
		}
		return true;
	}

}
